﻿using System;
using System.Collections.Generic;
using System.Text;

using System.Collections;
using System.IO;
using System.Threading;
using System.Runtime.Serialization.Formatters.Binary;


namespace RemoteObject
{
    public enum SiteType
    {
        cloudServer = 0,
        cloudSiteServer = 1,
        cloudClient = 2
    }

    public enum FileExchangeMode
    {
        Uploading,
        Downloading
    }

    [Serializable]
    public class Site
    {
        public string siteID;
        public string siteName;
        public string siteIP;
        public SiteType siteType;
    }

    [Serializable]
    public struct FileIndex
    {
        public Guid id;
        public string Name;
        public string Desc;
    }

    [Serializable]
    public class FileBlock
    {
        public long startPos;
        public long length;
        public int activeRate; //for upload and download.
    }

    [Serializable]
    public class FileIntegrity
    {
        public long totalFileSize;
        public List<FileBlock> remainBlocks = new List<FileBlock>();
        public double getCompleteRate()
        {
            long remainedFileSize = 0;
            foreach (FileBlock fb in remainBlocks)
            {
                remainedFileSize += fb.length;
            }
            return 1.0 - 1.0 * remainedFileSize / totalFileSize;
        }
    }

    [Serializable]
    public struct FileDisInfoItem
    {
        public Site locatedSite;
        public FileIntegrity integrtiy;
    }

    [Serializable]
    public class FileDisInfo
    {
        public string fileName;
        public List<FileDisInfoItem> fdiiList = new List<FileDisInfoItem>();
    }

    public class FileDisInfoManager
    {
        public static List<FileDisInfo> GetFileDisInfoList(string filter)
        {
            DirectoryInfo di = new DirectoryInfo(Common.FileDisInfoPath);
            FileInfo[] files = di.GetFiles("*" + Common.FileDisInfoFileExt);
            
            List<FileDisInfo> fdi_list = new List<FileDisInfo>();
            foreach( FileInfo fi in files )
            {
                FileDisInfo fdi;
                FileStream fs = null;
                try
                {
                    fs = new FileStream(fi.FullName, FileMode.Open, FileAccess.ReadWrite, FileShare.ReadWrite);
                    fdi = (new BinaryFormatter()).Deserialize(fs) as FileDisInfo;
                    if (filter.Length > 0)
                    {
                        if (String.Compare(filter.Trim(), fdi.fileName.Trim(), true) == 0)
                        {
                            fdi_list.Add(fdi);
                        }
                    }
                    else
                    {
                        fdi_list.Add(fdi);
                    }
                }
                catch
                {
                }

                if (fs != null)
                {
                    fs.Close();
                }
            }
            return fdi_list.Count == 0 ? null : fdi_list;
        }

        public static FileDisInfo GetFileDisInfo(string fileName)
        {
            string disFileName = GetFileDisInfoPath(fileName);
            if (File.Exists(disFileName))
            {
                FileStream fs = new FileStream(disFileName, FileMode.Open, FileAccess.ReadWrite, FileShare.ReadWrite);
                FileDisInfo fdi = (new BinaryFormatter()).Deserialize(fs) as FileDisInfo;
                fs.Close();
                return fdi;
            }
            return null;
        }
        
        public static string GetFileDisInfoPath(string fileName)
        {
            return Common.FileDisInfoPath + fileName + Common.FileDisInfoFileExt;
        }

        public static int UpdateFileDisInfoToIndexServer( string fileName, FileDisInfoItem _fdii )
        {
            string disFileName = GetFileDisInfoPath(fileName);
            Boolean bRemovingDisInfo = (_fdii.integrtiy.totalFileSize == -1 );

            BinaryFormatter bf = new BinaryFormatter();
            FileDisInfo fdi;
            FileStream fs;

            if (bRemovingDisInfo)
            {
                if (File.Exists(disFileName))
                {
                    fs = new FileStream(disFileName, FileMode.Open, FileAccess.ReadWrite, FileShare.ReadWrite);
                    fdi = bf.Deserialize(fs) as FileDisInfo;

                    Boolean deleteFile = false;
                    for (int i = 0; i < fdi.fdiiList.Count; i++)
                    {
                        FileDisInfoItem fdii = fdi.fdiiList[i];
                        if (fdii.locatedSite.siteID == _fdii.locatedSite.siteID)
                        {
                            if (fdi.fdiiList.Count == 1)
                            {
                                deleteFile = true;
                                break;
                            }
                            fdi.fdiiList.Remove(fdii);
                            fs.SetLength(0);
                            bf.Serialize(fs, fdi);
                            fs.Flush();
                            break;
                        }
                    }

                    fs.Close();

                    if (deleteFile)
                    {
                        //delete index file
                        File.Delete(disFileName);
                    }
                }
                return 1;
            }
            else
            {
                if (File.Exists(disFileName))
                {
                    fs = new FileStream(disFileName, FileMode.Open, FileAccess.ReadWrite, FileShare.ReadWrite);
                    fdi = bf.Deserialize(fs) as FileDisInfo;

                    bool alreadyHasSiteInfo = false;
                    for (int i = 0; i < fdi.fdiiList.Count; i++)
                    {
                        FileDisInfoItem fdii = fdi.fdiiList[i];
                        if (fdii.locatedSite.siteID == _fdii.locatedSite.siteID)
                        {
                            fdi.fileName = fileName;
                            fdi.fdiiList[i] = _fdii;
                            alreadyHasSiteInfo = true;
                            break;
                        }
                    }

                    if (!alreadyHasSiteInfo)
                    {
                        fdi.fdiiList.Add(_fdii);
                    }

                }
                else
                {
                    fs = new FileStream(disFileName, FileMode.Create, FileAccess.ReadWrite, FileShare.ReadWrite);
                    fdi = new FileDisInfo();
                    fdi.fileName = fileName;
                    fdi.fdiiList.Add(_fdii);
                }

                fs.SetLength(0);
                bf.Serialize(fs, fdi);
                fs.Flush();
                fs.Close();

                return 1;
            }

        }
    }

    [Serializable]
    public class TalkChannel
    {
        public long channelID;
        public Site clientSite;
        public DateTime lastHeartBeatTime = DateTime.Now;
    }


    public class IndexServer : BaseRemoteObject
    {
        public static long siteChannelID = 0;
        public static Dictionary<long, TalkChannel> aliveChannelList = new Dictionary<long, TalkChannel>();

        public List<Site> GetAliveSites(long channelID)
        {
            if (!aliveChannelList.ContainsKey(channelID))
                return null;

            List<Site> sites = new List<Site>();
            lock (aliveChannelList)
            {
                foreach (KeyValuePair<long, TalkChannel> a in aliveChannelList)
                {
                    TalkChannel tc = a.Value;
                    if ((DateTime.Now - tc.lastHeartBeatTime).TotalSeconds < 30)
                    {
                        sites.Add(tc.clientSite);
                    }
                }
            }
            return sites.Count != 0 ? sites : null;
        }

        public long ApplyTalkChannelID()
        {
            lock (aliveChannelList)
            {
                return ++siteChannelID;
            }
        }

        public TalkChannel StartTalkToIndexServer(long channelID, Site site)
        {
            TalkChannel tc = new TalkChannel();
            tc.channelID = channelID;
            tc.clientSite = site;
            lock (aliveChannelList)
            {
                aliveChannelList.Add(channelID, tc);
            }
            HeartBeatFromSite(channelID);
            return tc;
        }

        public int StopTalkToIndexServer(TalkChannel clientTc)
        {
            long channelID = clientTc.channelID;
            if (!aliveChannelList.ContainsKey(channelID))
                return -1;

            TalkChannel tc = aliveChannelList[channelID];
            lock (aliveChannelList)
            {
                aliveChannelList.Remove(channelID);
            }
            return 1;
        }

        public int HeartBeatFromSite(long channelID)
        {
            if (!aliveChannelList.ContainsKey(channelID))
                return -1;

            TalkChannel tc = aliveChannelList[channelID];
            tc.lastHeartBeatTime = DateTime.Now;

            return 1;
        }

        public int UpdateFileDisInfo(long channelID, string fileName, FileIntegrity fileIntegrity, Site fileLocatedSite )
        {
            if (!aliveChannelList.ContainsKey(channelID))
                return -1;

            TalkChannel tc = aliveChannelList[channelID];

            FileDisInfoItem fddi = new FileDisInfoItem();
            fddi.integrtiy = fileIntegrity;
            fddi.locatedSite = fileLocatedSite == null ? tc.clientSite : fileLocatedSite;

            return FileDisInfoManager.UpdateFileDisInfoToIndexServer(fileName, fddi );
        }

        public List<FileDisInfo> GetFileDisInfoList(long channelID, string filter)
        {
            if (!aliveChannelList.ContainsKey(channelID))
                return null;

            return FileDisInfoManager.GetFileDisInfoList(filter);
        }

        public FileDisInfo GetFileDisInfo(long channelID, string fileName)
        {
            if (!aliveChannelList.ContainsKey(channelID))
                return null;

            return FileDisInfoManager.GetFileDisInfo(fileName);
        }

        //Remote Task  ----------------------------------------------------------------------
        public static Hashtable remoteTaskReqList = new Hashtable();

        private static long remoteTaskID = 0;
        private static Thread threadRemoteTaskProcessThread = null;
        public long rmApplyUniqueTaskID()
        {
            lock (this)
            {
                if( threadRemoteTaskProcessThread  == null){
                    threadRemoteTaskProcessThread = new Thread(new ThreadStart(rmRemoteTaskProcessProc));
                    threadRemoteTaskProcessThread.Start();
                }
                return ++remoteTaskID;
            }
        }

        static void rmRemoteTaskProcessProc()
        {
            while(!Common.ApplicationQuit)
            {
                Thread.Sleep(1000);
                if (remoteTaskReqList.Count != 0)
                {
                    //remote task manage code
                }
            }
        }

        public int rmSubmitRemoteTaskToIndexServer( long channelID, RemoteTaskRequest remoteTaskReq )
        {
            if (!aliveChannelList.ContainsKey(channelID))
                return -1;

            TalkChannel tc = aliveChannelList[channelID];
            remoteTaskReq.requestSite = tc.clientSite;
            int subTaskID = 0;
            foreach (RemoteTaskBase st in remoteTaskReq.taskList)
            {
                st.parentRequestID = remoteTaskReq.requestID;
                st.taskID = ++subTaskID;
            }

            remoteTaskReqList.Add( remoteTaskReq.requestID, remoteTaskReq );

            return 1;
        }

        public int rmHeartBeatFromSite(long channelID, out List<RemoteTaskBase> tasksNeedMyAction, out List<RemoteTaskBase> doneTasksIRequested, List<RemoteTaskBase> myDoneTasks )
        {
            tasksNeedMyAction = doneTasksIRequested = null;

            if (!aliveChannelList.ContainsKey(channelID))
                return -1;

            TalkChannel tc = aliveChannelList[channelID];
            tc.lastHeartBeatTime = DateTime.Now;

            tasksNeedMyAction   = rmGetRemoteTaskFromIndexServer(channelID, RemoteTaskForWho.ActionSite,  RemoteTaskStatus.Idle, RemoteTaskStatus.ProcessingBySite );
            doneTasksIRequested = rmGetRemoteTaskFromIndexServer(channelID, RemoteTaskForWho.RequestSite, RemoteTaskStatus.Done, RemoteTaskStatus.RemoveFromIndexSever );

            if (myDoneTasks != null)
            {
                rmUpdateTaskStatus(channelID, myDoneTasks);
            }
            
            return 1;
        }

        public int rmUpdateSingleTaskStatus(RemoteTaskBase st)
        {
            //-------------------------------------------------------------
            if (remoteTaskReqList.Contains(st.parentRequestID))
            {
                RemoteTaskRequest rmReq = (RemoteTaskRequest)remoteTaskReqList[st.parentRequestID];
                foreach (RemoteTaskBase st2 in rmReq.taskList)
                {
                    if (st2.taskID == st.taskID)
                    {
                        st2.taskStatus = st.taskStatus;
                    }
                }
            }
            return 1;
        }

        public int rmUpdateTaskStatus(long channelID, List<RemoteTaskBase> stList )
        {
            if (!aliveChannelList.ContainsKey(channelID))
                return -1;

            TalkChannel tc = aliveChannelList[channelID];
            //-------------------------------------------------------------
            foreach (RemoteTaskBase st in stList)
            {
                rmUpdateSingleTaskStatus(st);
            }
            return 1;
        }

        public List<RemoteTaskBase> rmGetRemoteTaskFromIndexServer(long channelID, RemoteTaskForWho forWho, RemoteTaskStatus rtStatusFilter, RemoteTaskStatus rtStatusChangedTo)
        {
            if (!aliveChannelList.ContainsKey(channelID))
                return null;

            TalkChannel tc = aliveChannelList[channelID];
            List<RemoteTaskBase> stList = new List<RemoteTaskBase>();

            IDictionaryEnumerator myEnumerator = remoteTaskReqList.GetEnumerator();
            while (myEnumerator.MoveNext())
            {
                RemoteTaskRequest taskReq = (RemoteTaskRequest)myEnumerator.Value;
                if (forWho == RemoteTaskForWho.ActionSite)
                {
                    foreach (RemoteTaskBase st in taskReq.taskList)
                    {
                        if (st.taskStatus != rtStatusFilter)
                            continue;

                        if (st.actionSite.siteID == tc.clientSite.siteID)
                        {
                            if (rtStatusChangedTo != RemoteTaskStatus.Unchange)
                                st.taskStatus = rtStatusChangedTo;

                            stList.Add(st);
                        }
                    }

                    if (rtStatusChangedTo == RemoteTaskStatus.RemoveFromIndexSever)
                    {
                        for (int i = taskReq.taskList.Count - 1; i >= 0; i--)
                        {
                            RemoteTaskBase st = taskReq.taskList[i];
                            if (st.taskStatus == RemoteTaskStatus.RemoveFromIndexSever)
                            {
                                taskReq.taskList.RemoveAt(i);
                            }
                        }
                    }
                }
                if (forWho == RemoteTaskForWho.RequestSite)
                {
                    if (taskReq.requestSite.siteID != tc.clientSite.siteID)
                        continue;

                    foreach (RemoteTaskBase st in taskReq.taskList)
                    {
                        if (st.taskStatus != rtStatusFilter)
                            continue;
                        if (rtStatusChangedTo != RemoteTaskStatus.Unchange)
                            st.taskStatus = rtStatusChangedTo;
                        stList.Add(st);
                    }
                    if (rtStatusChangedTo == RemoteTaskStatus.RemoveFromIndexSever)
                    {
                        for (int i = taskReq.taskList.Count - 1; i >= 0; i--)
                        {
                            RemoteTaskBase st = taskReq.taskList[i];
                            if (st.taskStatus == RemoteTaskStatus.RemoveFromIndexSever)
                            {
                                taskReq.taskList.RemoveAt(i);
                            }
                        }
                    }
                }
            }
            return stList.Count == 0 ? null : stList;
        }
    }

    public enum RemoteTaskForWho
    {
        ActionSite = 0,
        RequestSite = 1,
    }

    public enum RemoteTaskType
    {
        Unknown = -1,
        FileExchange = 0,
        FileDelete = 1,
    }

    public enum RemoteTaskStatus
    {
        RemoveFromIndexSever = -2,
        Unchange = -1,
        Idle = 0,
        Done = 1,
        ProcessingBySite = 2,
    }

    [Serializable]
    public abstract class RemoteTaskBase
    {
        //attributes
        public RemoteTaskType   taskType = RemoteTaskType.Unknown;
        public RemoteTaskStatus taskStatus = RemoteTaskStatus.Idle;
        public Site actionSite;
        public long taskID;
        public long parentRequestID;

        //methods
        public abstract string TaskDesc();
    }

    [Serializable]
    public class RTaskFileUpload : RemoteTaskBase
    {
        public String fileName;
        public Site fromSite;
        public Site toSite;
        public RTaskFileUpload(String _fileName, Site _fromSite, Site _toSite)
        {
            taskType = RemoteTaskType.FileExchange;
            fileName = _fileName;
            actionSite = fromSite = _fromSite;
            toSite = _toSite;
        }

        public override string TaskDesc()
        {
            return String.Format("{0} {1} {2} {3}", ToString().Substring(ToString().LastIndexOf(".") + 1), fromSite.siteID, toSite.siteID, fileName );
        }
    }

    [Serializable]
    public class RTaskFileDelete : RemoteTaskBase
    {
        public String fileName;
        public RTaskFileDelete(String _fileName, Site _actionSite)
        {
            taskType = RemoteTaskType.FileDelete;
            fileName = _fileName;
            actionSite = _actionSite;
        }
        public override string TaskDesc()
        {
            return String.Format("{0} {1} {2}", ToString().Substring(ToString().LastIndexOf(".") + 1), actionSite.siteID, fileName);
        }
    }

    [Serializable]
    public class RemoteTaskRequest
    {
        public long requestID;
        public Site requestSite;
        public List<RemoteTaskBase> taskList;

        public RemoteTaskRequest(long reqID)
        {
            requestID = reqID;
        }
        public void AddRemoteTask(RemoteTaskBase newRemoteTask)
        {
            if (taskList == null)
            {
                taskList = new List<RemoteTaskBase>();
            }
            newRemoteTask.taskID = taskList.Count;
            newRemoteTask.parentRequestID = requestID;
            taskList.Add(newRemoteTask);
        }
    }

}
